﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SSharp.Core.Evaluator;
using SSharp.Core.Builtins;
using SSharp.Core.Reader;
using System.Reflection;
using SSharp.Core.DataTypes;
using System.Threading;

namespace SSharp {
	class REPL {
		private static Thread evalThread;
		private static AutoResetEvent startEval;
		private static AutoResetEvent evalFinished;
		private static object expression;
		private static IEnvironment environment;
		private static object lastResult;
		private static Exception lastErr;
		private static bool exit;

		public static void Start() {
			// we take the initial environment, and add an empty frame for all user definitions

			Console.WriteLine("S# v" + Assembly.GetExecutingAssembly().GetName().Version);

			startEval = new AutoResetEvent(false);
			evalFinished = new AutoResetEvent(false);
			evalThread = new Thread(EvaluationThread);
			evalThread.IsBackground = true;
			evalThread.Start();

			Frame newFrame = new Frame();
			newFrame["err"] = new Box(new PrimitiveProcedure("err", ErrorDetails));
			newFrame["exit"] = new Box(new PrimitiveProcedure("exit", o => exit = true));
			environment = InitialEnvironment.Environment.AddFrame(newFrame);

			//Console.TreatControlCAsInput = false;
			Console.CancelKeyPress += BreakKey;

			using (SchemeReader reader = new SchemeReader(Console.OpenStandardInput())) {
				while (true) {
					Console.Write("> ");
					try {
						expression = reader.ReadExpression();
					} catch (Exception ex) {
						Console.WriteLine(ex.Message);
						continue;
					}

					if (expression == null) {
						// end of stream
						break;
					}

					// call the evaluation thread
					startEval.Set();
					// wait for evaluation to finish
					evalFinished.WaitOne();

					if (exit) {
						// set from exit procedure
						break;
					}

					Console.WriteLine(lastResult);
				}
			}
			Console.WriteLine("Goodbye");
			Console.ReadKey(true);
		}

		private static void BreakKey(object sender, ConsoleCancelEventArgs e) {
			e.Cancel = true;
			Console.WriteLine("Aborting...");
			evalThread.Abort();
		}

		private static void EvaluationThread() {
			while (true) {
				startEval.WaitOne();

				try {
					lastResult = Eval.Evaluate(expression, environment);
				} catch (ThreadAbortException) {
					lastResult = "Evaluation aborted";
					Thread.ResetAbort();
				} catch (Exception ex) {
					lastResult = null;
					Console.WriteLine(ex.Message);
					Console.WriteLine("\ttype (err) to see more details");
					lastErr = ex;
				}

				evalFinished.Set();
			}
		}

		private static object ErrorDetails(object[] p) {
			if (lastErr == null) {
				return "No exception details to show";
			}

			// ignore parameters for this method
			Console.Clear();
			Console.WriteLine();
			Console.WriteLine(lastErr.GetType());
			Console.WriteLine(lastErr.Message);
			Console.WriteLine();
			Console.WriteLine(lastErr.StackTrace);
			Console.WriteLine();

			lastErr = null;
			return "";
		}
	}
}
